package org.xmdl.silk.interpreter;

import java.io.PrintStream;
import java.util.List;

import org.xmdl.silk.Assignment;
import org.xmdl.silk.Invocation;
import org.xmdl.silk.Method;
import org.xmdl.silk.PrintStatement;
import org.xmdl.silk.Program;
import org.xmdl.silk.Statement;
import org.xmdl.silk.ValueItem;
import org.xmdl.silk.Variable;
import org.xmdl.silk.wrap.ProgramWrap;
import org.xmdl.silk.wrap.StatementWrap;
import org.xmdl.silk.wrap.WrapUtils;


public class Interpreter extends Evaluator {

	private PrintStream out;

	public Interpreter() {
		super(new ExecutionFrame());
		this.out = System.out;
	}

	public void run(Program prog) {
		if (!(prog instanceof ProgramWrap)) {
			prog = WrapUtils.program(prog);
		}
		ProgramWrap p = (ProgramWrap) prog;
		p.accept(this);
	}

	
	@Override
	public boolean visitEnter(Method host) {
		return false;
	}

	@Override
	public boolean visitEnter(Assignment host) {
		Variable variable = host.getVariable();
		ValueItem valueExp = host.getValue();
		Value value = evaluate(valueExp);
		frame.setValue(variable, value);
		return false;
	}

	@Override
	public boolean visitEnter(PrintStatement host) {
		ValueItem item = host.getValue();
		Value value = evaluate(item);
		out.println(value);
		return false;
	}

	@Override
	public boolean visitEnter(Invocation host) {
		Method method = host.getMethod();
		List<ValueItem> parameters = host.getParameters();
		checkParameters(method, parameters);
		invoke(method, parameters);
		return false;
	}

	private void checkParameters(Method method, List<ValueItem> parameters) {
		// check matching parameters for the given method
		if (method.getParameters().size() != parameters.size())
			throw new UnmatchedParameterException(method, parameters);
	}

	private void invoke(Method m, List<ValueItem> params) {
		ExecutionFrame old = frame;
		frame = snapshot(m, params);
		List<Statement> stats = m.getStatements();
		for (Statement statement : stats) {
			((StatementWrap)statement).accept(this);
		}
		frame = old;
	}

	private ExecutionFrame snapshot(Method m, List<ValueItem> params) {
		List<Variable> parameters = m.getParameters();
		ExecutionFrame newFrame = new ExecutionFrame();
		for (int i = 0; i < parameters.size(); i++) {
			Variable var = parameters.get(i);
			ValueItem exp = params.get(i);
			Value val = evaluate(exp);
			newFrame.setValue(var, val);
		}
		return newFrame;
	}
	
}
